import sys
import os
import urllib
import urlparse
from exceptions import *
from BeautifulSoup import BeautifulSoup

class ParameterError(Exception):
    pass

class UnknowEncoding(Exception):
    pass

class WebPageLayoutChange(Exception):
    pass

# the following two functions comes from 
#   http://infohost.nmt.edu/tcc/projects/tccwebstats/ims/asciifyString.html
#   http://infohost.nmt.edu/tcc/projects/tccwebstats/ims/asciifyChar.html
# they will encode all non-ascii character, for example \xfe into %fe
def asciifyString(s):
    """Like urllib.quote(), but only quotes non-ASCII characters.

      [ s is a string ->
          return s with all characters >= 0x80 escaped using
          URL encoding ]
    """
    #-- 1 --
    # [ cleaned  :=  a list of the characters from s, with those
    #                >= 0x80 replaced by the URL-encoded equivalent ]
    cleaned  =  [ asciifyChar(c) for c in list(s) ]

    #-- 2 --
    return "".join ( cleaned )

def asciifyChar (c):
    """Escape c if it isn't ASCII, otherwise return c.

      [ c is a one-character string ->
          if ord(c) < 0x80 ->
            return c
          else ->
            return '%XX' where XX is ord(c) ]
    """
    if  ord(c) < 0x80:
        return c
    else:
        return "%%%2X" % ord(c)

def guess_encoding(string):
    if not string:
        raise ParameterErrr("Input string is empty!")

    encoding_candicates = ['utf-8', 'gbk', 'gb2312']
    for encoding in encoding_candicates:
        try:
            foo = unicode(string, encoding, 'strict')
            return encoding
        except:
            pass
    try:
        foo = string.encode('utf-8')
        return "unicode"
    except:
        raise UnknowEncoding("Failed to guess the encoding!")

def convert_string_encoding(string, _from, _to):
    if _from!='unicode':
        try:
            ustring = unicode(string, _from)
        except UnicodeDecodeError:
            return ''
    else:
        ustring = string
    try:
        result = ustring.encode(_to)
    except UnicodeEncodeError:
        return ''
    return result

def convert_gbk_value_unicode_to_gbk(ustring):
    """
    Convert some strange unicode string whose decimal value equals to 
    gbk encoding's decimal value. for example: 
            ustring = u'\xbe\xd5\xbb\xa8\xcc\xa8'
        but it should be:
            string = '\xbe\xd5\xbb\xa8\xcc\xa8' 
        in gbk encoding
    """
    assert isinstance(ustring, unicode)
    return ''.join([chr(ord(c)) for c in ustring])

def encode_url(url, url_encoding=None):
    if not url:
        raise ParameterError("Input string is empty!")
    encoding = url_encoding
    if not encoding:
        try:
           encoding = guess_encoding(url)
        except:
            logger = logging.getLogger("EncodeURL")
            logger.error("Can not get the encoding of input string!")
            raise

    if encoding!='gbk':
        gb_url = convert_string_encoding(url, encoding, 'gbk')
        if not gb_url:
            gb_url = convert_string_encoding(asciifyString(url), encoding, 'gbk')
            if not gb_url:
                print "failed to convert url string to gbk encoding!"
                return ''
    else:
        gb_url = url


    # in some case, the passed url is quoted partly, so to quote it again,
    # we need to unquote it first
    unquoted_gb_url = urllib.unquote(gb_url)
    url_splited_tuple = urllib.splitquery(unquoted_gb_url)
    url_splited_list = list(url_splited_tuple)
    if url_splited_list[0]:
        url_splited_list[0] = urllib.quote(url_splited_list[0], safe='/:')

    if url_splited_list[1]:
        url_splited_list[1] = urllib.quote(url_splited_list[1], safe='&=+')

    if url_splited_list[1]:
        return '?'.join(url_splited_list)
    else:
        return url_splited_list[0]

def get_soup(url, params=None):
    if params:
        resp = urllib.urlopen(url, params)
    else:
        resp = urllib.urlopen(url)
    page_source = resp.read()
    #try:
        #page_source = page_source.decode('gbk')
    #except:
        #return None
    return BeautifulSoup(page_source)

class SongInfo:
    def __init__(self):
        self.name = ''
        self.artist = ''
        self.url = ''
        self.lyric_url = ''
        self.size = 0.0
        self.type = ''
        self.speed = 0
        self.number = 0

class Downloader:
    def __init__(self, output_indent, dry_run_mode=False):
        self.output_indent = output_indent
        self.dry_run_mode = dry_run_mode
        if sys.platform == 'linux2':
            self.base_cmd = "axel "
            self.cmd_options = " -o %(name)s "
            self.cmd_args = " %(url)s"
        elif sys.platform == 'cygwin':
            self.base_cmd = "curl "
            self.cmd_options = " -o %(name)s "
            self.cmd_args = " %(url)s"
        else:
            raise Exception("Platform %s is not supported!" % sys.platform)


    def log(self, string):
        print self.output_indent + string

    def download(self, uname, url):
        if not url:
            raise ParameterError("Input url is not valid!")
        cmd_format = self.base_cmd
        if isinstance(uname, unicode):
            try:
                name = uname.encode('gbk')
            except UnicodeEncodeError:
                name = convert_gbk_value_unicode_to_gbk(uname)
        if name:
            if os.path.exists(name):
                print self.log("File(%s) has been downloaded before,"
                               "return now..."%name)
                return True
            #cmd_format += self.cmd_options
            name = "'" + name + "'"
            cmd_format += " -o " + name
            #cmd_format += " -o " + uname.encode('utf-8')
        else:
            cmd += " -O "
        cmd_format += self.cmd_args
        cmd = cmd_format%locals()
        retval = 0
        try:
            self.log("Start to run command: %s" % cmd)
            if not self.dry_run_mode:
                # if locale is utf-8, then we must encode 
                # cmd string before pass it to os.system
                #retval = os.system(cmd.encode('utf-8'))
                retval = os.system(cmd)
        except:
            self.log("Failed to download %s(%s)"%(name, url))
            return False
        self.log("Finish download %s with return value: %s"%(name,retval))
        return True

def error(msg):
    print >>sys.stderr, msg
    sys.exit(1)

#######################################33
### encoding ipython session
# 
#(Pdb) dir(e)
#Out[39]: 
#['__doc__',
# '__getitem__',
# '__init__',
# '__module__',
# '__str__',
# 'args',
# 'encoding',
# 'end',
# 'object',
# 'reason',
# 'start']
#(Pdb) e.args
#Out[39]: ('gbk', u'\xbe\xd5\xbb\xa8\xcc\xa8', 0, 1, 'illegal multibyte sequence')
#(Pdb) e.encoding
#Out[39]: 'gbk'
#(Pdb) e.object
#Out[39]: u'\xbe\xd5\xbb\xa8\xcc\xa8'
#(Pdb) e.reason
#Out[39]: 'illegal multibyte sequence'
#(Pdb) e.start
#Out[39]: 0
#(Pdb) e.end
#Out[39]: 1
#    def keepord(e):
#       return (u'#', e.end)
#
#    codecs.register_error('keepord', keepord)
#
#    # keepord will be called when there is any error during conversion 
#    us.encode('gbk', 'keepord') 

